package plc

import (
	"context"
	"encoding/binary"
	"errors"
	"fmt"
	hardware "gitee.com/wj008/hardware"
	"gitee.com/wj008/hardware/libs"
	log "github.com/wj008/goyee/logger"
	"math"
	"strings"
	"sync"
	"time"
)

type Device struct {
	sync.Mutex
	Id     int
	Kind   string
	Number string
	Conn   *Client
	Addr   string
	Rack   int
	Slot   int
	//频率
	Freq       int
	Watch      string
	WriteEn    int
	IsRun      bool //设备是否正常运行
	LastTime   int  //上次提交时间按
	Online     bool //设备是否正常运行
	chSend     chan *libs.WItem
	chSendOpen bool
	PutItems   func(d hardware.IDevice, data map[string]any)
}

func (d *Device) GetId() int {
	return d.Id
}
func (d *Device) GetNum() string {
	return d.Number
}
func (d *Device) GetKind() string {
	return d.Kind
}
func (d *Device) GetWatch() []string {
	result := make([]string, 0)
	items := strings.Split(d.Watch, ",")
	for _, item := range items {
		item = strings.TrimSpace(item)
		if item == "" {
			continue
		}
		parts := strings.SplitN(item, ":", 2)
		if len(parts) != 2 {
			continue
		}
		addr := strings.TrimSpace(parts[0])
		result = append(result, addr)
	}
	return result
}
func (d *Device) KeepRun(ctx context.Context) {
	timer := 1

	for {
		select {
		case <-ctx.Done():
			log.LPrintf(2, "PLC# Id:%d:服务被终止....\n", d.Id)
			return
		case <-time.After(time.Duration(timer) * time.Second):
			timer = 5
			log.LPrintf(2, "PLC# Id:%d:服务被重新启动....\n", d.Id)
			_ = d.Setup(ctx)
		}
	}
}

func (d *Device) Stop() {
	d.Lock()
	defer d.Unlock()
	d.IsRun = false
	if d.Conn != nil {
		_ = d.Conn.Close()
	}
	if d.chSendOpen {
		close(d.chSend)
		d.chSendOpen = false
	}
}

func (d *Device) Setup(ctx context.Context) (err error) {
	//链接PLC
	d.Lock()
	d.chSend = make(chan *libs.WItem, 10)
	d.chSendOpen = true
	d.Unlock()
	d.Conn, err = NewClient(d.Addr, d.Rack, d.Slot)
	if err != nil {
		return err
	}
	d.IsRun = true
	log.LPrintf(2, "MBUS# Id:%d:链接成功\n", d.Id)
	errChan := make(chan error, 2)
	// 启动读取协程
	go func() {
		err := d.readFromPort(ctx)
		if err != nil {
			errChan <- fmt.Errorf("读取协程错误: %w", err)
		}
	}()
	go func() {
		err := d.processWrites(ctx)
		if err != nil {
			errChan <- fmt.Errorf("写入协程错误: %w", err)
		}
	}()
	select {
	case <-ctx.Done():
		d.Stop()
		return ctx.Err()
	case err2 := <-errChan:
		log.LPrintln(2, err2.Error())
		d.Stop()
		return err2
	}
}

func (d *Device) readFromPort(ctx context.Context) (err error) {
	for {
		select {
		case <-ctx.Done():
			return
		case <-time.After(time.Duration(d.Freq) * time.Second):
			if d.Conn == nil || !d.Conn.IsConnect() {
				return fmt.Errorf("MBUS# Id:%d:链接已经丢失\n", d.Id)
			}
			err = d.CatchData()
			if err != nil {
				log.LPrintln(2, d.Id, err.Error())
				return err
			}
		}
	}
}

// processWrites 处理发送数据，发生错误时返回
func (d *Device) processWrites(ctx context.Context) error {
	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case data := <-d.chSend:
			if err := d.write(data); err != nil {
				// 返回写入错误，会被setup函数捕获
				return fmt.Errorf("写入数据失败: %w", err)
			}
		}
	}
}

func (d *Device) write(item *libs.WItem) (err error) {
	if d.Conn == nil || !d.Conn.IsConnect() {
		return fmt.Errorf("PLC# Id:%d:链接意外退出\n", d.Id)
	}
	if 1 != d.WriteEn {
		return fmt.Errorf("PLC# Id:%d:设备禁止写入任何数据\n", d.Id)
	}
	code := item.Addr
	typ := strings.ToUpper(item.Typ)
	value := item.Value
	tag, err := ParseTag(code, typ)
	if err != nil {
		return
	}
	buffer := make([]byte, 4)
	switch tag.Typ {
	case "BOOL":
		if tag.MBit > 7 || tag.MBit < 0 {
			err = fmt.Errorf("Db read bit is invalid")
			return
		}
		buffer[0] = 0
		if value.(bool) {
			buffer[0] = 1
		}
		if tag.Addr == "DB" {
			err = d.Conn.BitWriteDB(tag.DbNo, tag.Start, tag.MBit, tag.Size, buffer)
		} else if tag.Addr == "Q" {
			err = d.Conn.BitWriteQ(tag.Start, tag.MBit, tag.Size, buffer)
		} else if tag.Addr == "I" {
			err = d.Conn.BitWriteI(tag.Start, tag.MBit, tag.Size, buffer)
		} else if tag.Addr == "M" {
			err = d.Conn.BitWriteM(tag.Start, tag.MBit, tag.Size, buffer)
		} else if tag.Addr == "V" {
			err = d.Conn.BitWriteV(tag.Start, tag.MBit, tag.Size, buffer)
		}
		if err == nil {
			return
		}
		return errors.New("input code is invalid")
	case "U8":
		buffer[0] = value.(uint8)
		break
	case "I16":
		err = libs.SetValueAt(buffer, 0, value.(int16))
		//log.LPrintln(2,"tag.Typ", tag.Typ, "tag.Addr", tag.Addr, "tag.Start", tag.Start, "tag.MBit", tag.MBit, "tag.Size", tag.Size, "buffer", buffer, "tag.Code", tag.Code, "value", value)
		break
	case "U16":
		err = libs.SetValueAt(buffer, 0, value.(uint16))
		break
	case "I32":
		err = libs.SetValueAt(buffer, 0, value.(int32))
		break
	case "U32":
		err = libs.SetValueAt(buffer, 0, value.(uint32))
		break
	case "FLOAT":
		switch value.(type) {
		case float64:
			err = libs.SetRealAt(buffer, 0, float32(value.(float64)))
			break
		default:
			err = libs.SetRealAt(buffer, 0, value.(float32))
		}
		break
	}
	if err != nil {
		return
	}
	if tag.Addr == "DB" {
		err = d.Conn.AGWriteDB(tag.DbNo, tag.Start, tag.Size, buffer)
	} else if tag.Addr == "Q" {
		err = d.Conn.AGWriteQ(tag.Start, tag.Size, buffer)
	} else if tag.Addr == "I" {
		err = d.Conn.AGWriteI(tag.Start, tag.Size, buffer)
	} else if tag.Addr == "M" {
		err = d.Conn.AGWriteM(tag.Start, tag.Size, buffer)
	} else if tag.Addr == "V" {
		err = d.Conn.AGWriteV(tag.Start, tag.Size, buffer)
	}
	if err == nil {
		return
	}
	return errors.New("input code is invalid")
}

func (d *Device) LoadMaps() (libs.RMap, error) {
	result := make(libs.RMap)
	items := strings.Split(d.Watch, ",")
	for _, item := range items {
		// 去除可能的空格
		item = strings.TrimSpace(item)
		if item == "" {
			continue
		}
		// 按冒号分割地址和类型
		parts := strings.SplitN(item, ":", 2)
		if len(parts) != 2 {
			// 格式不正确，跳过该条目
			continue
		}
		addr := strings.TrimSpace(parts[0])
		typ := strings.TrimSpace(parts[1])
		// 以原始字符串作为键，创建RItem
		result[addr] = &libs.RItem{
			Addr: addr,
			Typ:  typ,
		}
	}
	return result, nil
}

// CatchData 抓取数据
func (d *Device) CatchData() (err error) {
	if d.Conn == nil || !d.Conn.IsConnect() {
		return fmt.Errorf("PLC# Id:%d:链接意外退出\n", d.Id)
	}
	if d.LoadMaps == nil {
		return nil
	}
	if d.PutItems == nil {
		return nil
	}
	tagMaps, err := d.LoadMaps()
	if err != nil {
		return fmt.Errorf("PLC# Id:%d:没有配置抓取字典\n", d.Id)
	}
	if tagMaps == nil {
		return fmt.Errorf("PLC# Id:%d:没有配置抓取字典\n", d.Id)
	}
	dataMap, err := d.MultiRead(tagMaps)
	if err != nil {
		return err
	}
	d.PutItems(d, dataMap)
	return nil
}

// SingleRead 简单读取
func (d *Device) SingleRead(code, typ string) (value any, err error) {
	d.Lock()
	defer d.Unlock()
	if d.Conn == nil || !d.Conn.IsConnect() {
		return nil, errors.New("PLC:链接失败")
	}
	tag, err := ParseTag(code, typ)
	if err != nil {
		return
	}
	buffer := make([]byte, 4)
	if tag.Addr == "DB" {
		err = d.Conn.AGReadDB(tag.DbNo, tag.Start, tag.Size, buffer)
	} else if tag.Addr == "Q" {
		err = d.Conn.AGReadQ(tag.Start, tag.Size, buffer)
	} else if tag.Addr == "I" {
		err = d.Conn.AGReadI(tag.Start, tag.Size, buffer)
	} else if tag.Addr == "M" {
		err = d.Conn.AGReadM(tag.Start, tag.Size, buffer)

	} else if tag.Addr == "V" {
		err = d.Conn.AGReadV(tag.Start, tag.Size, buffer)
	}
	if err != nil {
		return
	}
	switch tag.Typ {
	case "BOOL":
		if tag.MBit > 7 || tag.MBit < 0 {
			err = fmt.Errorf("Db read bit is invalid")
			return
		}
		mask := []byte{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}
		value = (buffer[0] & mask[tag.MBit]) != 0
		return
	case "U8":
		value = buffer[0]
		return
	case "I16":
		value = int16(binary.BigEndian.Uint16(buffer[0:]))
		return
	case "U16":
		value = binary.BigEndian.Uint16(buffer[0:])
		return
	case "I32":
		value = int32(binary.BigEndian.Uint32(buffer[0:]))
		return
	case "U32":
		value = binary.BigEndian.Uint32(buffer[0:])
		return
	case "FLOAT":
		value = float64(math.Float32frombits(binary.BigEndian.Uint32(buffer[0:])))
		return
	}
	return nil, errors.New("input code is invalid")
}

// SingleWrite 简单写入
func (d *Device) SingleWrite(code, typ string, value any) (err error) {
	d.Lock()
	defer d.Unlock()
	if !d.IsRun || !d.chSendOpen {
		return fmt.Errorf("MBUS# Id:%d:服务未运行\n", d.Id)
	}
	item := &libs.WItem{
		Addr:  code,
		Typ:   typ,
		Value: value,
	}
	select {
	case d.chSend <- item:
		return nil
	case <-time.After(1 * time.Second):
		return errors.New("发送数据超时")
	}
}

// MultiRead 批量读取
func (d *Device) MultiRead(maps libs.RMap) (valueMap map[string]any, err error) {
	d.Lock()
	defer d.Unlock()
	if d.Conn == nil || !d.Conn.IsConnect() {
		return nil, fmt.Errorf("PLC# Id:%d:链接意外退出\n", d.Id)
	}
	tc := NewTagBucket()
	for code, item := range maps {
		item.Typ = strings.ToUpper(item.Typ)
		item.Addr = strings.ToUpper(item.Addr)
		tag, err2 := ParseTag(item.Addr, item.Typ)
		if err2 != nil {
			return nil, fmt.Errorf("PLC# Id:%d:[%s][%s]解析有误,无法读取\n", d.Id, item.Addr, item.Typ)
		}
		tag.Code = code
		if !ValidType(tag.Typ) {
			continue
		}
		tc.Append(tag)
	}
	return d.tagsRead(tc)
}

func (d *Device) tagsRead(tc *TagBucket) (valueMap map[string]any, err error) {
	if d.Conn == nil || !d.Conn.IsConnect() {
		return nil, fmt.Errorf("PLC# Id:%d:链接意外退出\n", d.Id)
	}
	valueMap = make(map[string]any)
	for area, group := range tc.Groups {
		sizeLen := int(math.Ceil(float64(group.Length)/4) * 4)
		buffer := make([]byte, sizeLen)
		if group.Addr == "DB" {
			err = d.Conn.AGReadDB(group.DbNo, group.Start, group.Length, buffer)
			if err != nil {
				return nil, errors.New("批量读取[" + area + "]区块失败")
			}
		} else if group.Addr == "Q" {
			err = d.Conn.AGReadQ(group.Start, group.Length, buffer)
		} else if group.Addr == "I" {
			err = d.Conn.AGReadI(group.Start, group.Length, buffer)
		} else if group.Addr == "M" {
			err = d.Conn.AGReadM(group.Start, group.Length, buffer)
		} else if group.Addr == "V" {
			err = d.Conn.AGReadV(group.Start, group.Length, buffer)
		}
		if err != nil {
			return nil, errors.New("批量读取[" + group.Addr + "]区块失败")
		}
		for _, tag := range group.Elems {
			offset := tag.Start - group.Start
			if offset < 0 {
				continue
			}
			value, readErr := ReadTag(buffer, offset, tag)
			if readErr != nil {
				continue
			}
			valueMap[tag.Code] = value
		}
	}
	return valueMap, nil
}
